Skip to content

feat(openai): Instrument structured outputs (chat.completions.parse)#4416

Open
Nik-Reddy wants to merge 8 commits intoopen-telemetry:mainfrom
Nik-Reddy:feat/openai-structured-outputs-3449
Open

feat(openai): Instrument structured outputs (chat.completions.parse)#4416
Nik-Reddy wants to merge 8 commits intoopen-telemetry:mainfrom
Nik-Reddy:feat/openai-structured-outputs-3449

Conversation

@Nik-Reddy
Copy link
Copy Markdown

@Nik-Reddy Nik-Reddy commented Apr 13, 2026

Description

The OpenAI v2 instrumentation currently wraps chat.completions.create() but not chat.completions.parse(). The parse() method is used for structured outputs. Calls to parse() generate zero telemetry even when instrumentors are configured.

This PR adds instrumentation for both sync and async parse() methods, reusing the existing chat completion wrapper logic.

Fixes #3449

Changes

  • Added _is_parse_supported() version guard and wrap/unwrap calls for parse methods
  • Handle response_format being a Python type by recording json_schema as the output type attribute
  • 8 new test cases (sync + async x content capture x semconv variants)
  • 4 VCR cassettes for structured output calls

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

All 8 new tests pass. All 84 existing tests continue to pass.

Checklist:

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added

@Nik-Reddy Nik-Reddy requested a review from a team as a code owner April 13, 2026 00:17
@github-actions github-actions Bot requested a review from lmolkova April 13, 2026 00:17
@xrmx xrmx added the gen-ai Related to generative AI label Apr 13, 2026
Copy link
Copy Markdown
Member

@MikeGoldsmith MikeGoldsmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, thanks @Nik-Reddy. I've left some suggestions and please can you add a changelog entry.

@github-project-automation github-project-automation Bot moved this to Reviewed PRs that need fixes in Python PR digest Apr 13, 2026
@Nik-Reddy Nik-Reddy force-pushed the feat/openai-structured-outputs-3449 branch from 3ec82e7 to 6d00a12 Compare April 13, 2026 20:16
@Nik-Reddy
Copy link
Copy Markdown
Author

Nik-Reddy commented Apr 14, 2026

Hi @MikeGoldsmith, I've addressed all three of your review comments:

  1. Cached the result of _is_parse_supported() as self._parse_supported during _instrument()
  2. Extracted shared test definitions into structured_outputs_utils.py
  3. Added clarifying comment about parse/create relationship

Would appreciate a re-review when you get a chance. Thanks!

@Nik-Reddy Nik-Reddy force-pushed the feat/openai-structured-outputs-3449 branch from 6d00a12 to ce2065f Compare April 15, 2026 01:08
@Nik-Reddy
Copy link
Copy Markdown
Author

Nik-Reddy commented Apr 15, 2026

Rebased on latest main. All review feedback @MikeGoldsmith addressed:

  • Cached _is_parse_supported() result on the instrumentor instance
  • Extracted shared test definitions into structured_outputs_utils.py
  • Added clarifying comment for parse vs create dispatch

@Nik-Reddy Nik-Reddy requested a review from MikeGoldsmith April 15, 2026 01:16
@MikeGoldsmith
Copy link
Copy Markdown
Member

Thanks for the updates, the changes look good. One thing still to address — there are unused imports in test_structured_outputs.py (import json and server_attributes as ServerAttributes) as flagged by @dehanjl. You can clean these up by running the following locally:

uv run tox -e lint

This needs to be done before we can accept.

Also, I want to raise something directly: the responses to review feedback have been very fast and follows a pattern that suggests an automated agent may be posting comments on your behalf. As per our AGENTS.md at the root of this project, discussions on OpenTelemetry repositories are for humans only — AI-generated comments on issues and PRs are not permitted. Please ensure that all review responses and PR comments are written and posted by you directly. Thanks for understanding.

@Nik-Reddy Nik-Reddy requested a review from dehanjl April 16, 2026 03:45
@Nik-Reddy Nik-Reddy force-pushed the feat/openai-structured-outputs-3449 branch from 3cbb955 to 9a7ae07 Compare April 16, 2026 07:37
@Nik-Reddy
Copy link
Copy Markdown
Author

@dehanjl good catch on the unused imports. Cleaned those up, dropped json, ServerAttributes, EXPECTED_RESPONSE_CONTENT, and pytest (wasn't actually needed in the sync test file). Ran ruff locally and everything's passing now.

Rebased on latest main as well.

Copy link
Copy Markdown
Member

@MikeGoldsmith MikeGoldsmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ci is failing because of a new test that was added. precommit check is still failing too.

Please take a look 👍🏻

@Nik-Reddy Nik-Reddy force-pushed the feat/openai-structured-outputs-3449 branch 2 times, most recently from 208bc51 to 5db9b7a Compare April 16, 2026 21:00
@Nik-Reddy
Copy link
Copy Markdown
Author

@MikeGoldsmith, addressed all review feedback, including cached parse support check, shared test utilities, and semconv constants as per @lmolkova’s suggestion, along with the CI fixes. I will rebase on main today. Please let me know if any further adjustments are needed

@Nik-Reddy Nik-Reddy force-pushed the feat/openai-structured-outputs-3449 branch from 1ad49a1 to 6867155 Compare April 29, 2026 17:45
@lmolkova lmolkova requested review from Copilot and removed request for dehanjl May 1, 2026 01:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OpenAI v2 structured-output (chat.completions.parse) coverage to the existing OpenAI instrumentation so parse() calls emit the same telemetry as create().

Changes:

  • Wrap Completions.parse / AsyncCompletions.parse behind a version/feature guard in the instrumentor.
  • Extend request-attribute extraction to handle response_format passed as a Python type (e.g., a Pydantic model class).
  • Add sync/async structured output tests and VCR cassettes; update changelog entry.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/init.py Adds parse() support detection and instruments/uninstruments sync + async parse() methods.
instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/utils.py Updates request attribute handling for response_format when it is a Python type.
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_structured_outputs.py Adds sync parse() structured outputs tests (content/no-content; semconv variants).
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_async_structured_outputs.py Adds async parse() structured outputs tests (content/no-content; semconv variants).
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/structured_outputs_utils.py Adds shared prompt + Pydantic model for structured output tests.
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_structured_output_with_content.yaml Recorded structured output cassette (sync, content).
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_structured_output_no_content.yaml Recorded structured output cassette (sync, no content).
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_async_structured_output_with_content.yaml Recorded structured output cassette (async, content).
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_async_structured_output_no_content.yaml Recorded structured output cassette (async, no content).
CHANGELOG.md Documents the added parse() instrumentation feature.
Comments suppressed due to low confidence (1)

instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/utils.py:377

  • In create_chat_invocation(), when response_format is a plain string you set GenAIAttributes.GEN_AI_OPENAI_REQUEST_RESPONSE_FORMAT even on the new (util-genai / latest-experimental) path, while get_llm_request_attributes() uses gen_ai.output.type for latest-experimental. This makes the emitted attributes inconsistent and can drop gen_ai.output.type entirely for callers that pass response_format as a string. Consider setting GenAIAttributes.GEN_AI_OUTPUT_TYPE here as well (and mapping known values to the semconv enum where applicable).
    if (response_format := get_value(kwargs.get("response_format"))) is not None:
        # response_format may be string, object with a string in the `type` key,
        # or a type (e.g. Pydantic model class used with parse())
        if isinstance(response_format, type):
            invocation.attributes[GenAIAttributes.GEN_AI_OUTPUT_TYPE] = (
                GenAIAttributes.GenAiOutputTypeValues.JSON.value
            )
        elif isinstance(response_format, Mapping):
            if (
                response_format_type := get_value(response_format.get("type"))
            ) is not None:
                invocation.attributes[GenAIAttributes.GEN_AI_OUTPUT_TYPE] = (
                    response_format_type
                )
        else:
            invocation.attributes[
                GenAIAttributes.GEN_AI_OPENAI_REQUEST_RESPONSE_FORMAT
            ] = response_format

@lzchen
Copy link
Copy Markdown
Contributor

lzchen commented May 2, 2026

@Nik-Reddy

Might have to rebase again.

@Nik-Reddy Nik-Reddy force-pushed the feat/openai-structured-outputs-3449 branch 3 times, most recently from 5887c9e to 4bec562 Compare May 2, 2026 22:37
@Nik-Reddy
Copy link
Copy Markdown
Author

Nik-Reddy commented May 2, 2026

Pull request overview

Adds OpenAI v2 structured-output (chat.completions.parse) coverage to the existing OpenAI instrumentation so parse() calls emit the same telemetry as create().

Changes:

  • Wrap Completions.parse / AsyncCompletions.parse behind a version/feature guard in the instrumentor.
  • Extend request-attribute extraction to handle response_format passed as a Python type (e.g., a Pydantic model class).
  • Add sync/async structured output tests and VCR cassettes; update changelog entry.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/init.py Adds parse() support detection and instruments/uninstruments sync + async parse() methods.
instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/utils.py Updates request attribute handling for response_format when it is a Python type.
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_structured_outputs.py Adds sync parse() structured outputs tests (content/no-content; semconv variants).
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_async_structured_outputs.py Adds async parse() structured outputs tests (content/no-content; semconv variants).
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/structured_outputs_utils.py Adds shared prompt + Pydantic model for structured output tests.
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_structured_output_with_content.yaml Recorded structured output cassette (sync, content).
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_structured_output_no_content.yaml Recorded structured output cassette (sync, no content).
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_async_structured_output_with_content.yaml Recorded structured output cassette (async, content).
instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_async_structured_output_no_content.yaml Recorded structured output cassette (async, no content).
CHANGELOG.md Documents the added parse() instrumentation feature.
Comments suppressed due to low confidence (1)

@lmolkova, addressed the 3 Copilot suggestions, participants is now list[str], and I added assert response.choices[0].message.parsed is not None in both sync and async tests so we catch it if the wrapper ever breaks the parsed return.

Besides, wanted to check, is that level of assertion enough, or would you prefer something that also validates the shape of the parsed object?

@Nik-Reddy Nik-Reddy force-pushed the feat/openai-structured-outputs-3449 branch from 71ad47e to 8d79b51 Compare May 5, 2026 04:18
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented May 5, 2026

CLA Signed

The committers listed above are authorized under a signed CLA.

@Nik-Reddy Nik-Reddy force-pushed the feat/openai-structured-outputs-3449 branch from 8d79b51 to bad8407 Compare May 5, 2026 04:18
@Nik-Reddy Nik-Reddy force-pushed the feat/openai-structured-outputs-3449 branch from bad8407 to 76ee223 Compare May 5, 2026 05:03
@Nik-Reddy
Copy link
Copy Markdown
Author

@Nik-Reddy

Might have to rebase again.

@lzchen rebased on latest main. Also fixed the parse() wrappers after the positional args change in #4445 and the content_mode removal in #4315. CI should be clean now.

@Nik-Reddy Nik-Reddy force-pushed the feat/openai-structured-outputs-3449 branch from aaf42fb to ebbdc72 Compare May 6, 2026 05:33
@Nik-Reddy Nik-Reddy requested a review from Copilot May 6, 2026 05:35
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

@Nik-Reddy
Copy link
Copy Markdown
Author

@lzchen Rebased and addressed all copilot review comments, looks like the full test suite hasn't kicked off yet though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gen-ai Related to generative AI

Projects

Status: Reviewed PRs that need fixes

Development

Successfully merging this pull request may close these issues.

Instrument OpenAI structured outputs

7 participants